﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace WMemoryProfiler.Memory
{
    /// <summary>
    /// Describes an object graph from the first objects where we iteratively walk up the 
    /// object chain to find all parents up to a certain cutoff depth and width.
    /// </summary>
    public class ObjectNode
    {
        /// <summary>
        /// Object reference
        /// </summary>
        public object Object
        {
            get;
            private set;
        }

        /// <summary>
        /// Field where the object is stored. May be null if it is stored inside an array.
        /// </summary>
        public FieldInfo InField
        {
            get;
            private set;
        }

        HashSet<ObjectNode> _Parents;
        static HashSet<ObjectNode> _Empty = new HashSet<ObjectNode>();

        /// <summary>
        /// Set of parent nodes. The returned instance is never meant to be modified since 
        /// as default an empty list instance is returned to spare memory. Use AddParent instead.
        /// </summary>
        public ICollection<ObjectNode> Parents
        {
            get
            {
                return _Parents;
            }
        }

        /// <summary>
        /// Add a parent to the current node if parent was not already visited.
        /// </summary>
        /// <param name="visited"></param>
        /// <param name="parent"></param>
        /// <returns></returns>
        public bool AddParent(HashSet<object> visited, ObjectNode parent)
        {
            if (_Parents == _Empty)
            {
                _Parents = new HashSet<ObjectNode>();
            }

            bool lret = visited.Add(parent.Object);
            if (lret)
            {
                _Parents.Add(parent);
            }

            return lret;
        }

        /// <summary>
        /// Construct an ObjectNode object to build up an object graph.
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="inField"></param>
        public ObjectNode(object obj, FieldInfo inField)
        {
            Object = obj;
            InField = inField;
            _Parents = _Empty;
        }

        /// <summary>
        /// Count all parents and subparents
        /// </summary>
        public int TotalParents
        {
            get
            {
                int count = this.Parents.Count;
                Queue<ICollection<ObjectNode>> parents = new Queue<ICollection<ObjectNode>>();
                parents.Enqueue(this.Parents);
                while (parents.Count > 0)
                {
                    var p = parents.Dequeue();
                    count += p.Count;
                    foreach (var parent in p)
                    {
                        parents.Enqueue(parent.Parents);
                    }
                }

                return count;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return this.Object.GetHashCode();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            return Object.ReferenceEquals(this.Object, ((ObjectNode)obj).Object);
        }

    }
}
